package com.autumn.mybatis.session;

import java.sql.Connection;
import java.sql.SQLException;

import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;

/**
 * Autumn SqlSession 会话工厂
 * 
 * @author 杨昌国
 *
 *         2017-10-19 12:33:15
 */
public class AutumnSqlSessionFactory implements SqlSessionFactory {

	private final Configuration configuration;

	/**
	 * 当前线程会话
	 */
	private final ThreadLocal<SqlSession> SESSION_LOCAL = new ThreadLocal<SqlSession>();

	/**
	 * 移除会话
	 */
	public void removeCurrnetSession() {
		SESSION_LOCAL.remove();
	}

	/**
	 * 设置当前会话
	 * 
	 * @param session
	 *            会话
	 */
	void setCurrnetSession(SqlSession session) {
		SESSION_LOCAL.set(session);
	}

	/**
	 * 
	 * @param configuration
	 */
	public AutumnSqlSessionFactory(Configuration configuration) {
		this.configuration = configuration;
	}

	/**
	 * 打开当前会话,如果当前线程已存在，则会话当前线程的会话，否则创建新的会话并写入当会线程
	 */
	@Override
	public SqlSession openSession() {
		return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
	}

	/**
	 * 打开当前会话,如果当前线程已存在，则会话当前线程的会话，否则创建新的会话并写入当会线程
	 */
	@Override
	public SqlSession openSession(boolean autoCommit) {
		return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
	}

	/**
	 * 打开当前会话,如果当前线程已存在，则会话当前线程的会话，否则创建新的会话并写入当会线程
	 */
	@Override
	public SqlSession openSession(ExecutorType execType) {
		return openSessionFromDataSource(execType, null, false);
	}

	/**
	 * 打开当前会话,如果当前线程已存在，则会话当前线程的会话，否则创建新的会话并写入当会线程
	 */
	@Override
	public SqlSession openSession(TransactionIsolationLevel level) {
		return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
	}

	/**
	 * 打开当前会话,如果当前线程已存在，则会话当前线程的会话，否则创建新的会话并写入当会线程
	 */
	@Override
	public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
		return openSessionFromDataSource(execType, level, false);
	}

	/**
	 * 打开当前会话,如果当前线程已存在，则会话当前线程的会话，否则创建新的会话并写入当会线程
	 */
	@Override
	public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
		return openSessionFromDataSource(execType, null, autoCommit);
	}

	/**
	 * 打开当前会话,如果当前线程已存在，则会话当前线程的会话，否则创建新的会话并写入当会线程
	 */
	@Override
	public SqlSession openSession(Connection connection) {
		return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
	}

	/**
	 * 打开当前会话,如果当前线程已存在，则会话当前线程的会话，否则创建新的会话并写入当会线程
	 */
	@Override
	public SqlSession openSession(ExecutorType execType, Connection connection) {
		return openSessionFromConnection(execType, connection);
	}

	@Override
	public Configuration getConfiguration() {
		return configuration;
	}

	private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
			boolean autoCommit) {
		SqlSession session = SESSION_LOCAL.get();
		if (session != null) {
			return session;
		}
		Transaction tx = null;
		try {
			final Environment environment = configuration.getEnvironment();
			final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
			tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
			final Executor executor = configuration.newExecutor(tx, execType);
			return new AutumnSqlSession(this, configuration, executor, autoCommit);
		} catch (Exception e) {
			// may have fetched a connection so lets call
			closeTransaction(tx); 
			// close()						
			removeCurrnetSession();
			throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
		} finally {
			ErrorContext.instance().reset();
		}
	}

	private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
		SqlSession session = SESSION_LOCAL.get();
		if (session != null) {
			return session;
		}
		try {
			boolean autoCommit;
			try {
				autoCommit = connection.getAutoCommit();
			} catch (SQLException e) {
				// Failover to true, as most poor drivers
				// or databases won't support transactions
				autoCommit = true;
			}
			final Environment environment = configuration.getEnvironment();
			final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
			final Transaction tx = transactionFactory.newTransaction(connection);
			final Executor executor = configuration.newExecutor(tx, execType);
			return new AutumnSqlSession(this, configuration, executor, autoCommit);
		} catch (Exception e) {
			removeCurrnetSession();
			throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
		} finally {
			ErrorContext.instance().reset();
		}
	}

	private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
		if (environment == null || environment.getTransactionFactory() == null) {
			return new ManagedTransactionFactory();
		}
		return environment.getTransactionFactory();
	}

	private void closeTransaction(Transaction tx) {
		if (tx != null) {
			try {
				tx.close();
			} catch (SQLException ignore) {
				// Intentionally ignore. Prefer previous error.
			}
		}
	}

}
